using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;

namespace Gko.Utils
{
	/// <summary>
	/// 	Extensions to <see cref = "T:System.Collections.Generic.IEnumerable`1" />
	/// </summary>
	public static class EnumerableExtensions
	{
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "2")]
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "1")]
		[SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures")]
		public static IEnumerable<Pair<T1, T2>> Zip<T1, T2>(this IEnumerable<T1> first, IEnumerable<T2> second)
		{
			return first.Zip(second, (a, b) => Pair.Create(a, b));
		}

		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "2")]
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "1")]
		public static IEnumerable<TResult> Zip<T1, T2, TResult>(this IEnumerable<T1> first, IEnumerable<T2> second, Func<T1, T2, TResult> zipper)
		{
			return first.Zip(second, zipper, (v, i) => i, (v, i) => i);
		}

		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "2")]
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "1")]
		public static IEnumerable<TResult> Zip<T1, T2, TKey, TResult>(this IEnumerable<T1> first, IEnumerable<T2> second, Func<T1, T2, TResult> zipper, Func<T1, TKey> firstKeySelector, Func<T2, TKey> secondKeySelector)
		{
			return from a in first.Select(value => Pair.Create(value, firstKeySelector(value)))
			       join b in second.Select(value => Pair.Create(value, secondKeySelector(value))) on a.Second equals b.Second
			       select zipper(a.First, b.First);
		}

		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "2")]
		[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "1")]
		public static IEnumerable<TResult> Zip<T1, T2, TKey, TResult>(this IEnumerable<T1> first, IEnumerable<T2> second, Func<T1, T2, TResult> zipper, Func<T1, int, TKey> firstKeySelector, Func<T2, int, TKey> secondKeySelector)
		{
			return from a in first.Select((value, index) => Pair.Create(value, firstKeySelector(value, index)))
			       join b in second.Select((value, index) => Pair.Create(value, secondKeySelector(value, index))) on a.Second equals b.Second
			       select zipper(a.First, b.First);
		}

		public static IEnumerable<T> ToSequence<T>(this T item)
		{
			yield return item;
		}

		public static IEnumerable<T> ToSequence<T>(this T item, params T[] items)
		{
			return item.ToSequence().Concat(items);
		}

		public static IEnumerable<object> ToGeneric(this IEnumerable source)
		{
			return source.Cast<object>();
		}

		/// <summary>
		/// 	Generates a sequence that contains one repeated value.
		/// </summary>
		/// <typeparam name = "T">The type of the value to be repeated in the result sequence.</typeparam>
		/// <param name = "element">The value to be repeated.</param>
		/// <param name = "count">The number of times to repeat the value in the generated sequence.</param>
		/// <returns>An <see cref = "T:System.Collections.Generic.IEnumerable`1" /> that contains a repeated value.</returns>
		/// <exception cref = "System.ArgumentOutOfRangeException">count is less than 0.</exception>
		public static IEnumerable<T> Repeat<T>(this T element, int count)
		{
			return Enumerable.Repeat(element, count);
		}

		/// <summary>
		/// 	Produces the set difference of two sequences by using the default equality comparer to compare values.
		/// </summary>
		/// <typeparam name = "T">The type of the elements of the input sequences.</typeparam>
		/// <param name = "sequence">An <see cref = "T:System.Collections.Generic.IEnumerable`1" /> whose elements that are not equals to the element will be returned.</param>
		/// <param name = "element">The element that occurs in the sequence will cause those elements to be removed from the returned sequence.</param>
		/// <returns>A sequence that does not contains the element.</returns>
		/// <exception cref = "System.ArgumentNullException">sequence or element is null</exception>
		public static IEnumerable<T> Except<T>(this IEnumerable<T> sequence, T element)
		{
			return sequence.Except(element.Repeat(1));
		}

		/// <summary>
		/// 	Produces the set difference of two sequences by using the default equality comparer to compare values.
		/// </summary>
		/// <typeparam name = "T">The type of the elements of the input sequences.</typeparam>
		/// <param name = "first">An <see cref = "T:System.Collections.Generic.IEnumerable`1" /> whose elements that are not also in second will be returned.</param>
		/// <param name = "second">An <see cref = "T:System.Array" /> whose elements that also occur in the first sequence will cause those elements to be removed from the returned sequence.</param>
		/// <returns>A sequence that contains the set difference of the elements of two sequences.</returns>
		/// <exception cref = "System.ArgumentNullException">first or second is null.</exception>
		public static IEnumerable<T> Except<T>(this IEnumerable<T> first, params T[] second)
		{
			return first.Except(second as IEnumerable<T>);
		}

		/// <summary>
		/// 	Determines whether a sequence contains a specified element by using the default equality comparer.
		/// </summary>
		/// <typeparam name = "T">The type of the elements</typeparam>
		/// <param name = "value">The value to locate in the sequence.</param>
		/// <param name = "values">A sequence in which to locate a value.</param>
		/// <returns>
		/// 	<c>true</c> if the values contains an element that has the specified value; otherwise, <c>false</c>.
		/// </returns>
		/// <exception cref = "System.ArgumentNullException">value is null.</exception>
		public static bool IsOneOf<T>(this T value, params T[] values)
		{
			return values.Contains(value);
		}

		/// <summary>
		/// 	Determines whether a sequence contains a specified element by using a specified <see cref = "T:System.Collections.Generic.IEqualityComparer`1" />.
		/// </summary>
		/// <typeparam name = "T">The type of the elements of source</typeparam>
		/// <param name = "value">The value to locate in the sequence.</param>
		/// <param name = "comparer">An equality comparer to compare values.</param>
		/// <param name = "values">A sequence in which to locate a value.</param>
		/// <returns>
		/// 	<c>true</c> if source sequence contains an element that has the specified value; otherwise, <c>false</c>.
		/// </returns>
		/// <exception cref = "System.ArgumentNullException">value is null.</exception>
		public static bool IsOneOf<T>(this T value, IEqualityComparer<T> comparer, params T[] values)
		{
			return values.Contains(value, comparer);
		}

		/// <summary>
		/// 	Performs the specified action on each element of the <see cref = "T:System.Collections.Generic.IEnumerable`1" />.
		/// </summary>
		/// <typeparam name = "T">The type of elements in the sequence</typeparam>
		/// <param name = "sequence">The sequence of elements.</param>
		/// <param name = "action">The <see cref = "T:System.Action`1" /> delegate to perform on each element of the <see cref = "T:System.Collections.Generic.IEnumerable`1" />.</param>
		public static void ForEach<T>(this IEnumerable<T> sequence, Action<T> action)
		{
			foreach (var item in sequence) {
				action(item);
			}
		}

		/// <summary>
		/// 	Performs the specified action on each element of the <see cref = "T:System.Collections.Generic.IEnumerable`1" />.
		/// </summary>
		/// <typeparam name = "T">The type of elements in the sequence</typeparam>
		/// <param name = "sequence">The sequence of elements.</param>
		/// <param name = "action">The <see cref = "T:System.Action`2" /> delegate to perform on each element of the <see cref = "T:System.Collections.Generic.IEnumerable`1" />.</param>
		public static void ForEach<T>(this IEnumerable<T> sequence, Action<T, int> action)
		{
			var index = 0;
			foreach (var item in sequence) {
				action(item, index++);
			}
		}

		/// <summary>
		/// 	Performs the specified action on each element of the <see cref = "T:System.Collections.IEnumerable" />.
		/// </summary>
		/// <param name = "sequence">The sequence of elements.</param>
		/// <param name = "action">The <see cref = "T:System.Action`1" /> delegate to perform on each element of the <see cref = "T:System.Collections.IEnumerable" />.</param>
		public static void ForEach(this IEnumerable sequence, Action<object> action)
		{
			foreach (var item in sequence) {
				action(item);
			}
		}

		/// <summary>
		/// 	Performs the specified action on each element of the <see cref = "T:System.Collections.IEnumerable" />.
		/// </summary>
		/// <param name = "sequence">The sequence of elements.</param>
		/// <param name = "action">The <see cref = "T:System.Action`2" /> delegate to perform on each element of the <see cref = "T:System.Collections.IEnumerable" />.</param>
		public static void ForEach(this IEnumerable sequence, Action<object, int> action)
		{
			var index = 0;
			foreach (var item in sequence) {
				action(item, index++);
			}
		}

		/// <summary>
		/// 	Returns a read-only <see cref = "T:System.Collections.Generic.IList`1"></see> wrapper for the current collection.
		/// </summary>
		/// <typeparam name = "T">The type of elements in the sequence</typeparam>
		/// <param name = "sequence">The sequence.</param>
		/// <returns>A <see cref = "T:System.Collections.ObjectModel.ReadOnlyCollection`1"></see> that acts as a read-only wrapper around the current <see cref = "T:System.Collections.Generic.List`1"></see>.</returns>
		public static ReadOnlyCollection<T> AsReadOnly<T>(this IEnumerable<T> sequence)
		{
			var array = sequence as T[];
			if (array != null) {
				return Array.AsReadOnly(array);
			}

			var list = sequence as List<T>;
			if (list != null) {
				return list.AsReadOnly();
			}

			return sequence.ToList().AsReadOnly();
		}

		/// <summary>
		/// 	Returns the source sequence, or an empty sequence if <paramref name = "source" /> is <c>null</c>.
		/// </summary>
		/// <param name = "source">The source sequence.</param>
		/// <returns>The source sequence, or an empty sequence if <paramref name = "source" /> is <c>null</c>.</returns>
		/// <remarks>
		/// 	See <a href = "http://code.logos.com/blog/2008/03/emptyifnull.html">EmptyIfNull</a>.
		/// </remarks>
		public static IEnumerable<T> EmptyIfNull<T>(this IEnumerable<T> source)
		{
			return source ?? Enumerable.Empty<T>();
		}

		public static IEnumerable<T> EmptyIfNull<TSource, T>(this TSource source, Func<TSource, IEnumerable<T>> selector) where TSource : class
		{
			return source == null ? Enumerable.Empty<T>() : selector(source).EmptyIfNull();
		}

		public static IEnumerable<T> EmptyIfNull<TSource, T>(this TSource? source, Func<TSource, IEnumerable<T>> selector) where TSource : struct 
		{
			return source.HasValue ? selector(source.Value).EmptyIfNull() : Enumerable.Empty<T>();
		}

		/// <summary>
		/// 	Sorts the elements of a sequence in ascending order according to a key.
		/// </summary>
		/// <param name = "source">A sequence of values to order.</param>
		/// <param name = "keySelector">A function to extract a key from an element.</param>
		/// <returns>An <see cref = "IEnumerable{TSource}" /> whose elements are sorted according to a key.</returns>
		/// <remarks>
		/// 	This method only sorts as much of <paramref name = "source" /> as is required to yield the
		/// 	elements that are requested from the return value.
		/// </remarks>
		public static IOrderedEnumerable<TSource> LazyOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
		{
			return LazyOrderBy(source, keySelector, null, false);
		}

		/// <summary>
		/// 	Sorts the elements of a sequence in ascending order according to a key.
		/// </summary>
		/// <param name = "source">A sequence of values to order.</param>
		/// <param name = "keySelector">A function to extract a key from an element.</param>
		/// <param name = "comparer">An <see cref = "IComparer{T}" /> to compare keys.</param>
		/// <returns>An <see cref = "IEnumerable{TSource}" /> whose elements are sorted according to a key.</returns>
		/// <remarks>
		/// 	This method only sorts as much of <paramref name = "source" /> as is required to yield the
		/// 	elements that are requested from the return value.
		/// </remarks>
		public static IOrderedEnumerable<TSource> LazyOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
		{
			return LazyOrderBy(source, keySelector, comparer, false);
		}

		/// <summary>
		/// 	Sorts the elements of a sequence in descending order according to a key.
		/// </summary>
		/// <param name = "source">A sequence of values to order.</param>
		/// <param name = "keySelector">A function to extract a key from an element.</param>
		/// <returns>An <see cref = "IEnumerable{TSource}" /> whose elements are sorted according to a key.</returns>
		/// <remarks>
		/// 	This method only sorts as much of <paramref name = "source" /> as is required to yield the
		/// 	elements that are requested from the return value.
		/// </remarks>
		public static IOrderedEnumerable<TSource> LazyOrderByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
		{
			return LazyOrderBy(source, keySelector, null, true);
		}

		/// <summary>
		/// 	Sorts the elements of a sequence in descending order according to a key.
		/// </summary>
		/// <param name = "source">A sequence of values to order.</param>
		/// <param name = "keySelector">A function to extract a key from an element.</param>
		/// <param name = "comparer">An <see cref = "IComparer{T}" /> to compare keys.</param>
		/// <returns>An <see cref = "IEnumerable{TSource}" /> whose elements are sorted according to a key.</returns>
		/// <remarks>
		/// 	This method only sorts as much of <paramref name = "source" /> as is required to yield the
		/// 	elements that are requested from the return value.
		/// </remarks>
		public static IOrderedEnumerable<TSource> LazyOrderByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer)
		{
			return LazyOrderBy(source, keySelector, comparer, true);
		}

		private static IOrderedEnumerable<TSource> LazyOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending)
		{
			if (source == null) {
				throw new ArgumentNullException("source");
			}
			if (keySelector == null) {
				throw new ArgumentNullException("keySelector");
			}

			return new OrderedEnumerable<TSource>(source, new ElementComparer<TSource, TKey>(keySelector, comparer ?? Comparer<TKey>.Default, descending, null));
		}

		/// <summary>
		/// 	Computes the sum of a sequence of <see cref = "T:System.Nullable`1" /> values.
		/// </summary>
		/// <param name = "source">A sequence of <see cref = "T:System.Nullable`1" /> values to calculate the sum of.</param>
		/// <returns>The sum of the values in the sequence.</returns>
		/// <remarks>
		/// 	This method returns zero if <paramref name = "source" /> contains no elements.
		/// </remarks>
		public static decimal? NullableSum(this IEnumerable<decimal?> source)
		{
			return source.Aggregate((decimal?)0, (sum, value) => sum + value);
		}

		/// <summary>
		/// 	Computes the sum of a sequence of <see cref = "T:System.Nullable`1" /> values.
		/// </summary>
		/// <param name = "source">A sequence of <see cref = "T:System.Nullable`1" /> values to calculate the sum of.</param>
		/// <returns>The sum of the values in the sequence.</returns>
		/// <remarks>
		/// 	This method returns zero if <paramref name = "source" /> contains no elements.
		/// </remarks>
		public static double? NullableSum(this IEnumerable<double?> source)
		{
			return source.Aggregate((double?)0, (sum, value) => sum + value);
		}

		/// <summary>
		/// 	Computes the sum of a sequence of <see cref = "T:System.Nullable`1" /> values.
		/// </summary>
		/// <param name = "source">A sequence of <see cref = "T:System.Nullable`1" /> values to calculate the sum of.</param>
		/// <returns>The sum of the values in the sequence.</returns>
		/// <remarks>
		/// 	This method returns zero if <paramref name = "source" /> contains no elements.
		/// </remarks>
		public static int? NullableSum(this IEnumerable<int?> source)
		{
			return source.Aggregate((int?)0, (sum, value) => {
				return checked(sum + value);
			});
		}

		/// <summary>
		/// 	Computes the sum of a sequence of <see cref = "T:System.Nullable`1" /> values.
		/// </summary>
		/// <param name = "source">A sequence of <see cref = "T:System.Nullable`1" /> values to calculate the sum of.</param>
		/// <returns>The sum of the values in the sequence.</returns>
		/// <remarks>
		/// 	This method returns zero if <paramref name = "source" /> contains no elements.
		/// </remarks>
		public static long? NullableSum(this IEnumerable<long?> source)
		{
			return source.Aggregate((long?)0, (sum, value) => {
				return checked(sum + value);
			});
		}

		/// <summary>
		/// 	Computes the sum of a sequence of <see cref = "T:System.Nullable`1" /> values.
		/// </summary>
		/// <param name = "source">A sequence of <see cref = "T:System.Nullable`1" /> values to calculate the sum of.</param>
		/// <returns>The sum of the values in the sequence.</returns>
		/// <remarks>
		/// 	This method returns zero if <paramref name = "source" /> contains no elements.
		/// </remarks>
		public static float? NullableSum(this IEnumerable<float?> source)
		{
			return source.Aggregate((float?)0, (sum, value) => sum + value);
		}

		/// <summary>
		/// 	Returns all the elements in the specified collection that are not null.
		/// </summary>
		/// <param name = "source">An <see cref = "T:System.Collections.Generic.IEnumerable`1" /> to filter.</param>
		/// <returns>An <see cref = "T:System.Collections.Generic.IEnumerable`1" /> that contains elements from the input sequence that are not null.</returns>
		/// <remarks>
		/// 	See <a href = "http://code.logos.com/blog/2010/04/wherenotnull_extension_method.html">WhereNotNull Extension Method</a>.
		/// </remarks>
		public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T> source) where T : class
		{
			if (source == null) {
				throw new ArgumentNullException("source");
			}
			return source.Where(x => x != null);
		}

		/// <summary>
		/// 	Returns all the elements in the specified collection that are not null.
		/// </summary>
		/// <param name = "source">An <see cref = "T:System.Collections.Generic.IEnumerable`1" /> to filter.</param>
		/// <returns>An <see cref = "T:System.Collections.Generic.IEnumerable`1" /> that contains elements from the input sequence that are not null.</returns>
		/// <remarks>
		/// 	See <a href = "http://code.logos.com/blog/2010/04/wherenotnull_extension_method.html">WhereNotNull Extension Method</a>.
		/// </remarks>
		public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> source) where T : struct
		{
			if (source == null) {
				throw new ArgumentNullException("source");
			}
			return from t in source
			       where t.HasValue
			       select t.Value;
		}
	}
}
